/* -------------------------- htmldataprocessor.js -------------------------- */
(function() {

   VED.htmlDataProcessor = new Class({
      initialize: function( editor ) {
         var dataFilter, htmlFilter,
            that = this;

         this.editor = editor;

         this.dataFilter = dataFilter = new VED.htmlParser.filter();
         this.htmlFilter = htmlFilter = new VED.htmlParser.filter();

         this.writer = new VED.htmlParser.basicWriter();

         dataFilter.addRules( defaultDataFilterRules );
         dataFilter.addRules( createBogusAndFillerRules( editor, 'data' ) );
         htmlFilter.addRules( defaultHtmlFilterRules );
         htmlFilter.addRules( createBogusAndFillerRules( editor, 'html' ) );

         editor.on( 'toHtml', function( evt ) {
            var evtData = evt.data,
               data = evtData.dataValue;

            // The source data is already HTML, but we need to clean
            // it up and apply the filter.
            data = protectSource( data, editor );

            // Protect content of textareas. (#9995)
            // Do this before protecting attributes to avoid breaking:
            // <textarea><img src="..." /></textarea>
            data = protectElements( data, protectTextareaRegex );

            // Before anything, we must protect the URL attributes as the
            // browser may changing them when setting the innerHTML later in
            // the code.
            data = protectAttributes( data );

				// Protect Inner Links (<la id="#">...</la>)
				data = protectLa( data );

            // Protect elements than can't be set inside a DIV. E.g. IE removes
            // style tags from innerHTML. (#3710)
            data = protectElements( data, protectElementsRegex );

            // Certain elements has problem to go through DOM operation, protect
            // them by prefixing 'ved' namespace. (#3591)
            data = protectElementsNames( data );

            // All none-IE browsers ignore self-closed custom elements,
            // protecting them into open-close. (#3591)
            data = protectSelfClosingElements( data );

            // Compensate one leading line break after <pre> open as browsers
            // eat it up. (#5789)
            data = protectPreFormatted( data );

            var fixBin = evtData.context || editor.editable().getName(),
               isPre;

            // Old IEs loose formats when load html into <pre>.
            if ( VED.env.ie && VED.env.version < 9 && fixBin == 'pre' ) {
               fixBin = 'div';
               data = '<pre>' + data + '</pre>';
               isPre = 1;
            }

            // Call the browser to help us fixing a possibly invalid HTML
            // structure.
            var el = editor.document.createElement( fixBin );
            // Add fake character to workaround IE comments bug. (#3801)
            el.setHtml( 'a' + data );
            data = el.getHtml().substr( 1 );

            // Restore shortly protected attribute names.
            data = data.replace( new RegExp( ' data-ved-' + VED.rnd + '-', 'ig' ), ' ' );

            isPre && ( data = data.replace( /^<pre>|<\/pre>$/gi, '' ) );

            // Unprotect "some" of the protected elements at this point.
            data = unprotectElementNames( data );

            data = unprotectElements( data );

            // Restore the comments that have been protected, in this way they
            // can be properly filtered.
            data = unprotectRealComments( data );

            // Now use our parser to make further fixes to the structure, as
            // well as apply the filter.
            evtData.dataValue = VED.htmlParser.fragment.fromHtml( data, evtData.context, evtData.fixForBody === false ? false : getFixBodyTag( editor.config ) );
         }, null, null, 5 );

         editor.on( 'toHtml', function( evt ) {
            evt.data.dataValue.filterChildren( that.dataFilter, true );
         }, null, null, 10 );

         editor.on( 'toHtml', function( evt ) {
            var evtData = evt.data,
               data = evtData.dataValue,
               writer = new VED.htmlParser.basicWriter();

            data.writeChildrenHtml( writer );
            data = writer.getHtml( true );

            // Protect the real comments again.
            evtData.dataValue = protectRealComments( data );
         }, null, null, 15 );


         editor.on( 'toDataFormat', function( evt ) {
            evt.data.dataValue = VED.htmlParser.fragment.fromHtml(
               evt.data.dataValue, editor.editable().getName(), getFixBodyTag( editor.config ) );
         }, null, null, 5 );

         editor.on( 'toDataFormat', function( evt ) {
            evt.data.dataValue.filterChildren( that.htmlFilter, true );
         }, null, null, 10 );

         editor.on( 'toDataFormat', function( evt ) {
            var data = evt.data.dataValue,
               writer = that.writer;

            writer.reset();
            data.writeChildrenHtml( writer );
            data = writer.getHtml( true );

            // Restore those non-HTML protected source. (#4475,#4880)
				data = unprotectLa( data );
            data = unprotectRealComments( data );
            data = unprotectSource( data, editor );

            evt.data.dataValue = data;
         }, null, null, 15 );
      },

      toHtml: function( data, context, fixForBody, dontFilter ) {
         var editor = this.editor;

         // Fall back to the editable as context if not specified.
         if ( !context && context !== null )
            context = editor.editable().getName();

         return editor.fire( 'toHtml', {
            dataValue: data,
            context: context,
            fixForBody: fixForBody,
            dontFilter: !!dontFilter
         } ).dataValue;
      },

      toDataFormat: function( html ) {
         return this.editor.fire( 'toDataFormat', {
            dataValue: html
         }).dataValue;
      }
   });

   // Produce a set of filtering rules that handles bogus and filler node at the
   // end of block/pseudo block, in the following consequence:
   // 1. elements:<block> - this filter removes any bogus node, then check
   // if it's an empty block that requires a filler.
   // 2. elements:<br> - After cleaned with bogus, this filter checks the real
   // line-break BR to compensate a filler after it.
   //
   // Terms definitions:
   // filler: An element that's either <BR> or &NBSP; at the end of block that established line height.
   // bogus: Whenever a filler is proceeded with inline content, it becomes a bogus which is subjected to be removed.
   //
   // Various forms of the filler:
   // In output HTML: Filler should be consistently &NBSP; <BR> at the end of block is always considered as bogus.
   // In Wysiwyg HTML: Browser dependent - Filler is either BR for non-IE, or &NBSP; for IE, <BR> is NEVER considered as bogus for IE.
   function createBogusAndFillerRules( editor, type ) {
      function createFiller( isOutput ) {
         return isOutput || VED.env.ie ?
                new VED.htmlParser.text( '\xa0' ) :
                new VED.htmlParser.element( 'br', { 'data-ved-bogus': 1 } );
      }

      // This text block filter, remove any bogus and create the filler on demand.
      function blockFilter( isOutput, fillEmptyBlock ) {

         return function( block ) {

            // DO NOT apply the filer if it's a fragment node.
            if ( block.type == VED.NODE_DOCUMENT_FRAGMENT )
               return;

            cleanBogus( block );

            // [Opera] it's mandatory for the filler to present inside of empty block when in WYSIWYG.
            if ( ( ( VED.env.opera && !isOutput ) ||
                  ( typeof fillEmptyBlock == 'function' ? fillEmptyBlock( block ) !== false : fillEmptyBlock ) ) &&
                   isEmptyBlockNeedFiller( block ) )
            {
               block.add( createFiller( isOutput ) );
            }
         };
      }

      // Append a filler right after the last line-break BR, found at the end of block.
      function brFilter( isOutput ) {
         return function ( br ) {

            // DO NOT apply the filer if parent's a fragment node.
            if ( br.parent.type == VED.NODE_DOCUMENT_FRAGMENT )
               return;

            var attrs = br.attributes;
            // Dismiss BRs that are either bogus or eol marker.
            if ( 'data-ved-bogus' in attrs ||
                   'data-ved-eol' in attrs ) {
               delete attrs [ 'data-ved-bogus' ];
               return;
            }

            // Judge the tail line-break BR, and to insert bogus after it.
            var next = getNext( br ), previous = getPrevious( br );

            if ( !next && isBlockBoundary( br.parent ) )
               append( br.parent, createFiller( isOutput ) );
            else if ( isBlockBoundary( next ) && previous && !isBlockBoundary( previous ) ) {
               insertBefore( next, createFiller( isOutput ) );
            }
         };
      }

      // Determinate whether this node is potentially a bogus node.
      function maybeBogus( node, atBlockEnd ) {

         // BR that's not from IE DOM, except for a EOL marker.
         if ( !( isOutput && VED.env.ie ) &&
                node.type == VED.NODE_ELEMENT && node.name == 'br' &&
                !node.attributes[ 'data-ved-eol' ] )
            return true;

         var match;
         // NBSP, possibly.
         if ( node.type == VED.NODE_TEXT &&
                ( match = node.value.match( tailNbspRegex ) ) )
         {
            // We need to separate tail NBSP out of a text node, for later removal.
            if ( match.index ) {
               insertBefore( node, new VED.htmlParser.text( node.value.substring( 0, match.index ) ) );
               node.value = match[ 0 ];
            }

            // From IE DOM, at the end of a text block, or before block boundary.
            if ( VED.env.ie && isOutput && ( !atBlockEnd || node.parent.name in textBlockTags ) )
               return true;

            // From the output.
            if ( !isOutput ) {
               var previous = node.previous;

               // Following a line-break at the end of block.
               if ( previous && previous.name == 'br' )
                  return true;

               // Or a single NBSP between two blocks.
               if ( !previous || isBlockBoundary( previous ) )
                  return true;
            }
         }

         return false;
      }

      // Removes all bogus inside of this block, and to convert fillers into the proper form.
      function cleanBogus( block ) {
         var bogus = [];
         var last = getLast( block ), node, previous;
         if ( last ) {

            // Check for bogus at the end of this block.
            // e.g. <p>foo<br /></p>
            maybeBogus( last, 1 ) && bogus.push( last );

            while ( last ) {

               // Check for bogus at the end of any pseudo block contained.
               if ( isBlockBoundary( last ) &&
                      ( node = getPrevious( last ) ) &&
                      maybeBogus( node ) )
               {
                  // Bogus must have inline proceeding, instead single BR between two blocks,
                  // is considered as filler, e.g. <hr /><br /><hr />
                  if ( ( previous = getPrevious( node ) ) && !isBlockBoundary( previous ) )
                     bogus.push( node );
                  // Convert the filler into appropriate form.
                  else {
                     insertAfter( node, createFiller( isOutput ) );
                     removeFromParent( node );
                  }
               }

               last = last.previous;
            }
         }

         // Now remove all bogus collected from above.
         for ( var i = 0 ; i < bogus.length ; i++ )
            removeFromParent( bogus[ i ] );
      }

      // Judge whether it's an empty block that requires a filler node.
      function isEmptyBlockNeedFiller( block ) {

         // DO NOT fill empty editable in IE.
         if ( !isOutput && VED.env.ie && block.type == VED.NODE_DOCUMENT_FRAGMENT )
            return false;

         // 1. For IE version >=8,  empty blocks are displayed correctly themself in wysiwiyg;
         // 2. For the rest, at least table cell and list item need no filler space. (#6248)
         if ( !isOutput && VED.env.ie &&
                ( document.documentMode > 7 ||
                   block.name in VED.dtd.tr ||
                   block.name in VED.dtd.$listItem ) ) {
            return false;
         }

         var last = getLast( block );
         return !last || block.name == 'form' && last.name == 'input' ;
      }

      var rules = { elements: {} };
      var isOutput = type == 'html';

      // Build the list of text blocks.
      var textBlockTags = VED.tools.extend( {}, blockLikeTags );
      for ( var i in textBlockTags ) {
         if ( !( '#' in dtd[ i ] ) )
            delete textBlockTags[ i ];
      }

      for ( i in textBlockTags )
         rules.elements[ i ] = blockFilter( isOutput, editor.config.fillEmptyBlocks !== false );

      // Editable element is to be checked separately.
      rules.root = blockFilter( isOutput );
      rules.elements.br = brFilter( isOutput );
      return rules;
   }

   function getFixBodyTag( config ) {
      return ( config.enterMode != VED.ENTER_BR && config.autoParagraph !== false ) ? config.enterMode == VED.ENTER_DIV ? 'div' : 'p' : false;
   }

   // Regex to scan for &nbsp; at the end of blocks, which are actually placeholders.
   // Safari transforms the &nbsp; to \xa0. (#4172)
   var tailNbspRegex = /(?:&nbsp;|\xa0)$/;

   var protectedSourceMarker = '{ved_protected}';

   function getLast( node ) {
      var last = node.children[ node.children.length - 1 ];
      while ( last && isEmpty( last ) )
         last = last.previous;
      return last;
   }

   function getNext( node ) {
      var next = node.next;
      while ( next && isEmpty( next ) )
         next = next.next;
      return next;
   }

   function getPrevious( node ) {
      var previous = node.previous;
      while ( previous && isEmpty( previous ) )
         previous = previous.previous;
      return previous;
   }

   // Judge whether the node is an ghost node to be ignored, when traversing.
   function isEmpty( node ) {
      return node.type == VED.NODE_TEXT &&
        !node.value.trim() ||
        node.type == VED.NODE_ELEMENT &&
        node.attributes[ 'data-ved-bookmark' ];
   }

   // Judge whether the node is a block-like element.
   function isBlockBoundary( node ) {
      return node &&
                ( node.type == VED.NODE_ELEMENT && node.name in blockLikeTags ||
                   node.type == VED.NODE_DOCUMENT_FRAGMENT );
   }

   function insertAfter( node, insertion ) {
      var children = node.parent.children;
      var index = VED.tools.indexOf( children, node );
      children.splice( index + 1, 0, insertion );
      var next = node.next;
      node.next = insertion;
      insertion.previous = node;
      insertion.parent = node.parent;
      insertion.next = next;
   }

   function insertBefore( node, insertion ) {
      var children = node.parent.children;
      var index = VED.tools.indexOf( children, node );
      children.splice( index, 0, insertion );
      var prev = node.previous;
      node.previous = insertion;
      insertion.next = node;
      insertion.parent = node.parent;
      if ( prev ) {
         insertion.previous = prev;
         prev.next = insertion;
      }
   }

   function append( parent, node ) {
      var last = parent.children[ parent.children.length -1 ];
      parent.children.push( node );
      node.parent = parent;
      if ( last ) {
         last.next = node;
         node.previous = last;
      }
   }

   function removeFromParent( node ) {
      var children = node.parent.children;
      var index = VED.tools.indexOf( children, node );
      var previous = node.previous, next = node.next;
      previous && ( previous.next = next );
      next && ( next.previous = previous );
      children.splice( index, 1 );
   }

   function getNodeIndex( node ) {
      var parent = node.parent;
      return parent ? VED.tools.indexOf( parent.children, node ) : -1;
   }

   var dtd = VED.dtd;

   // Define orders of table elements.
   var tableOrder = [ 'caption', 'colgroup', 'col', 'thead', 'tfoot', 'tbody' ];

   // List of all block elements.
   var blockLikeTags = VED.tools.extend( {}, dtd.$blockLimit, dtd.$block );

   var defaultDataFilterRules = {
      elements: {},
      attributeNames: [
         // Event attributes (onXYZ) must not be directly set. They can become
         // active in the editing area (IE|WebKit).
         [ ( /^on/ ), 'data-ved-pa-on' ]
      ]
   };

   var defaultHtmlFilterRules = {
      elementNames: [
         // Remove the "ved:" namespace prefix.
         [ ( /^ved:/ ), '' ],

         // Ignore <?xml:namespace> tags.
         [ ( /^\?xml:namespace$/ ), '' ]
      ],

      attributeNames: [
         // Attributes saved for changes and protected attributes.
         [ ( /^data-ved-(saved|pa)-/ ), '' ],

         // All "data-ved-" attributes are to be ignored.
         [ ( /^data-ved-.*/ ), '' ],

         [ 'hidefocus', '' ]
      ],

      elements: {
         $: function( element ) {
            var attribs = element.attributes;

            if ( attribs ) {
               // Elements marked as temporary are to be ignored.
               if ( attribs[ 'data-ved-temp' ] )
                  return false;

               // Remove duplicated attributes - #3789.
               var attributeNames = [ 'name', 'href', 'src' ],
                  savedAttributeName;
               for ( var i = 0; i < attributeNames.length; i++ ) {
                  savedAttributeName = 'data-ved-saved-' + attributeNames[ i ];
                  savedAttributeName in attribs && ( delete attribs[ attributeNames[ i ] ] );
               }
            }

            return element;
         },

         // The contents of table should be in correct order (#4809).
         table: function( element ) {
               // Clone the array as it would become empty during the sort call.
               var children = element.children.slice( 0 );
               children.sort( function ( node1, node2 ) {
                  var index1, index2;

                  // Compare in the predefined order.
                  if ( node1.type == VED.NODE_ELEMENT &&
                         node2.type == node1.type ) {
                     index1 = VED.tools.indexOf( tableOrder, node1.name );
                     index2 = VED.tools.indexOf( tableOrder, node2.name );
                  }

                  // Make sure the sort is stable, if no order can be established above.
                  if ( !( index1 > -1 && index2 > -1 && index1 != index2 ) ) {
                     index1 = getNodeIndex( node1 );
                     index2 = getNodeIndex( node2 );
                  }

                  return index1 > index2 ? 1 : -1;
               });
         },

         embed: function( element ) {
            var parent = element.parent;

            // If the <embed> is child of a <object>, copy the width
            // and height attributes from it.
            if ( parent && parent.name == 'object' ) {
               var parentWidth = parent.attributes.width,
                  parentHeight = parent.attributes.height;
               parentWidth && ( element.attributes.width = parentWidth );
               parentHeight && ( element.attributes.height = parentHeight );
            }
         },
         // Restore param elements into self-closing.
         param: function( param ) {
            param.children = [];
            param.isEmpty = true;
            return param;
         },

         // Remove empty link but not empty anchor.(#3829)
         a: function( element ) {
            if ( !( element.children.length || element.attributes.name || element.attributes[ 'data-ved-saved-name' ] ) ) {
               return false;
            }
         },

         // Remove dummy span in webkit.
         span: function( element ) {
            if ( element.attributes[ 'class' ] == 'Apple-style-span' )
               delete element.name;
         },

         html: function( element ) {
            delete element.attributes.contenteditable;
            delete element.attributes[ 'class' ];
         },

         body: function( element ) {
            delete element.attributes.spellcheck;
            delete element.attributes.contenteditable;
         },

         style: function( element ) {
            var child = element.children[ 0 ];
            child && child.value && ( child.value = child.value.trim() );

            if ( !element.attributes.type )
               element.attributes.type = 'text/css';
         },

         title: function( element ) {
            var titleText = element.children[ 0 ];

            // Append text-node to title tag if not present (i.e. non-IEs) (#9882).
            !titleText && append( element, titleText = new VED.htmlParser.text() );

            // Transfer data-saved title to title tag.
            titleText.value = element.attributes[ 'data-ved-title' ] || '';
         }
      },

      attributes: {
         'class': function( value, element ) {
            // Remove all class names starting with "ved_".
            return value.replace( /(?:^|\s+)ved_[^\s]*/g, '' ).ltrim() || false;
         }
      }
   };

   if ( VED.env.ie ) {
      // IE outputs style attribute in capital letters. We should convert
      // them back to lower case, while not hurting the values (#5930)
      defaultHtmlFilterRules.attributes.style = function( value, element ) {
         return value.replace( /(^|;)([^\:]+)/g, function( match ) {
            return match.toLowerCase();
         });
      };
   }

   function protectReadOnly( element ) {
      var attrs = element.attributes;

      // We should flag that the element was locked by our code so
      // it'll be editable by the editor functions (#6046).
      if ( attrs.contenteditable != "false" )
         attrs[ 'data-ved-editable' ] = attrs.contenteditable ? 'true' : 1;

      attrs.contenteditable = "false";
   }

   function unprotectReadyOnly( element ) {
      var attrs = element.attributes;
      switch ( attrs[ 'data-ved-editable' ] ) {
         case 'true':
            attrs.contenteditable = 'true';
            break;
         case '1':
            delete attrs.contenteditable;
            break;
      }
   }
   // Disable form elements editing mode provided by some browsers. (#5746)
   for ( var i in { input:1,textarea:1 } ) {
      defaultDataFilterRules.elements[ i ] = protectReadOnly;
      defaultHtmlFilterRules.elements[ i ] = unprotectReadyOnly;
   }

   var protectElementRegex = /<(a|area|img|input|source)\b([^>]*)>/gi,
      protectAttributeRegex = /\s(on\w+|href|src|name)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi;

   // Note: we use lazy star '*?' to prevent eating everything up to the last occurrence of </style> or </textarea>.
   var protectElementsRegex = /(?:<style(?=[ >])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi,
      protectTextareaRegex = /(<textarea(?=[ >])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi,
      encodedElementsRegex = /<ved:encoded>([^<]*)<\/ved:encoded>/gi;

   var protectElementNamesRegex = /(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,
      unprotectElementNamesRegex = /(<\/?)ved:((?:html|body|head|title)[^>]*>)/gi;

   var protectSelfClosingRegex = /<ved:(param|embed)([^>]*?)\/?>(?!\s*<\/ved:\1)/gi;

   var protectLaRegex = /<la[^>]*?(id=("[^"]*"|'[^']*'))[^>]*?>(.*?)<\/la>/gi,
      unprotectLaRegex = /<a\b([^>]*xtagname="la"[^>]*)>(.*?)<\/a>/gi;

   function protectLa( html ) {
      return html.replace( protectLaRegex, '<a $1 href="javascript://" xtagname="la">$3</a>' );
   }

   function unprotectLa( html ) {
      return html.replace( unprotectLaRegex, function( element, attributes, html ) {
         return '<la' + attributes.replace( / xtagname="la"/, '' )
                                 .replace( / href="[^"]*"/, '' ) + '>' + html + '</la>'
      });
   }

   function protectAttributes( html ) {
      return html.replace( protectElementRegex, function( element, tag, attributes ) {
         return '<' + tag + attributes.replace( protectAttributeRegex, function( fullAttr, attrName ) {
            // Avoid corrupting the inline event attributes (#7243).
            // We should not rewrite the existed protected attributes, e.g. clipboard content from editor. (#5218)
            if ( !( /^on/ ).test( attrName ) && attributes.indexOf( 'data-ved-saved-' + attrName ) == -1 ) {
               fullAttr = fullAttr.slice( 1 ); // Strip the space.
               return ' data-ved-saved-' + fullAttr + ' data-ved-' + VED.rnd + '-' + fullAttr;
            }

            return fullAttr;
         }) + '>';
      });
   }

   function protectElements( html, regex ) {
      return html.replace( regex, function( match, tag, content ) {
         // Encode < and > in textarea because this won't be done by a browser, since
         // textarea will be protected during passing data through fix bin.
         if ( match.indexOf( '<textarea' ) === 0 )
            match = tag + unprotectRealComments( content ).replace( /</g, '&lt;' ).replace( />/g, '&gt;' ) + '</textarea>';

         return '<ved:encoded>' + encodeURIComponent( match ) + '</ved:encoded>';
      });
   }

   function unprotectElements( html ) {
      return html.replace( encodedElementsRegex, function( match, encoded ) {
         return decodeURIComponent( encoded );
      });
   }

   function protectElementsNames( html ) {
      return html.replace( protectElementNamesRegex, '$1ved:$2' );
   }

   function unprotectElementNames( html ) {
      return html.replace( unprotectElementNamesRegex, '$1$2' );
   }

   function protectSelfClosingElements( html ) {
      return html.replace( protectSelfClosingRegex, '<ved:$1$2></ved:$1>' );
   }

   function protectPreFormatted( html ) {
      return VED.env.opera ? html : html.replace( /(<pre\b[^>]*>)(\r\n|\n)/g, '$1$2$2' );
   }

   function protectRealComments( html ) {
      return html.replace( /<!--(?!{ved_protected})[\s\S]+?-->/g, function( match ) {
         return '<!--' + protectedSourceMarker +
            '{C}' +
            encodeURIComponent( match ).replace( /--/g, '%2D%2D' ) +
            '-->';
      });
   }

   function unprotectRealComments( html ) {
      return html.replace( /<!--\{ved_protected\}\{C\}([\s\S]+?)-->/g, function( match, data ) {
         return decodeURIComponent( data );
      });
   }

   function unprotectSource( html, editor ) {
      var store = editor._.dataStore;

      return html.replace( /<!--\{ved_protected\}([\s\S]+?)-->/g, function( match, data ) {
         return decodeURIComponent( data );
      }).replace( /\{ved_protected_(\d+)\}/g, function( match, id ) {
         return store && store[ id ] || '';
      });
   }

   function protectSource( data, editor ) {
      var protectedHtml = [],
         protectRegexes = editor.config.protectedSource,
         store = editor._.dataStore || ( editor._.dataStore = { id:1 } ),
         tempRegex = /<\!--\{ved_temp(comment)?\}(\d*?)-->/g;

      var regexes = [
         // Script tags will also be forced to be protected, otherwise
         // IE will execute them.
         ( /<script[\s\S]*?<\/script>/gi ),

         // <noscript> tags (get lost in IE and messed up in FF).
         /<noscript[\s\S]*?<\/noscript>/gi
      ].concat( protectRegexes );

      // First of any other protection, we must protect all comments
      // to avoid loosing them (of course, IE related).
      // Note that we use a different tag for comments, as we need to
      // transform them when applying filters.
      data = data.replace( ( /<!--[\s\S]*?-->/g ), function( match ) {
         return '<!--{ved_tempcomment}' + ( protectedHtml.push( match ) - 1 ) + '-->';
      });

      for ( var i = 0; i < regexes.length; i++ ) {
         data = data.replace( regexes[ i ], function( match ) {
            match = match.replace( tempRegex, // There could be protected source inside another one. (#3869).
            function( $, isComment, id ) {
               return protectedHtml[ id ];
            });

            // Avoid protecting over protected, e.g. /\{.*?\}/
            return ( /ved_temp(comment)?/ ).test( match ) ? match : '<!--{ved_temp}' + ( protectedHtml.push( match ) - 1 ) + '-->';
         });
      }
      data = data.replace( tempRegex, function( $, isComment, id ) {
         return '<!--' + protectedSourceMarker +
            ( isComment ? '{C}' : '' ) +
            encodeURIComponent( protectedHtml[ id ] ).replace( /--/g, '%2D%2D' ) +
            '-->';
      });

      // Different protection pattern is used for those that
      // live in attributes to avoid from being HTML encoded.
      return data.replace( /(['"]).*?\1/g, function( match ) {
         return match.replace( /<!--\{ved_protected\}([\s\S]+?)-->/g, function( match, data ) {
            store[ store.id ] = decodeURIComponent( data );
            return '{ved_protected_' + ( store.id++ ) + '}';
         });
      });
   }
})();





